//
// System.Runtime.Serialization.SerializationCallbacks.cs
//
// Author:
//   Robert Jordan (robertj@gmx.net)
//
// Copyright (C) 2006 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#ifndef SerializationCallbacks_h
#define SerializationCallbacks_h

#include <CSharpPropertyMethods.h>
#include <REvents.h>
#include <System/EventArgs.h>
#include <System/NotImplementedException.h>
#include <System/Runtime/Serialization/ISafeSerializationData.h>
#include <System/Reflection/BindingFlags.h>

namespace Riccsson
{
	namespace System
	{
        // Not implemented.
        namespace Collections
		{
            class ArrayList;
            class Hashtable;
		}
		namespace Runtime
		{
			namespace Serialization
			{
	            /*internal*/ class SerializationCallbacks sealed
	            {
		            public: CREATE_DELEGATE1(Void, CallbackHandler, GC_PTR<StreamingContext>);

		            private: static GC_PTR<Collections::Hashtable> cache;
		            private: static GC_PTR<object> cache_lock;

                    STATIC(SerializationCallbacks)
					{
                        cache = new Collections::Hashtable ();
                        cache_lock = new object ();
					}

		            readonly Collections::ArrayList onSerializingList;
		            readonly Collections::ArrayList onSerializedList;
		            readonly Collections::ArrayList onDeserializingList;
		            readonly Collections::ArrayList onDeserializedList;

		            public: PROP3_GET(bool, HasSerializingCallbacks)
					{
			            return onSerializingList != null;
		            }

		            public: PROP3_GET(bool, HasSerializedCallbacks)
			        {
						return onSerializedList != null;
		            }

		            public: PROP3_GET(bool, HasDeserializingCallbacks)
			        {
						return onDeserializingList != null;
		            }

		            public: PROP3_GET(bool, HasDeserializedCallbacks)
			        {
						return onDeserializedList != null;
		            }

		            const Reflection::BindingFlags DefaultBindingFlags;

		            public: SerializationCallbacks (Type* type)
					    : DefaultBindingFlags(Reflection::BindingFlags::Public | Reflection::BindingFlags::NonPublic | Reflection::BindingFlags::Instance | Reflection::BindingFlags::DeclaredOnly)
					{
                        
			            onSerializingList   = GetMethodsByAttribute (type, typeof (OnSerializingAttribute));
			            onSerializedList    = GetMethodsByAttribute (type, typeof (OnSerializedAttribute));
			            onDeserializingList = GetMethodsByAttribute (type, typeof (OnDeserializingAttribute));
			            onDeserializedList  = GetMethodsByAttribute (type, typeof (OnDeserializedAttribute));
		            }


		            static GC_PTR<Collections::ArrayList> GetMethodsByAttribute (Type* type, Type* attr)
		            {
			            GC_PTR<Collections::ArrayList> list = new Collections::ArrayList ();

			            GC_PTR<Type> t = type;
			            while (t != typeof (object)) {
				            int count = 0;

				            foreach (MethodInfo* mi : t->GetMethods (DefaultBindingFlags)) {
					            if (mi->IsDefined (attr, false)) {
						            list->Add (mi);
						            count++;
					            }
				            }

				            // FIXME: MS.NET is checking for this with the verifier at assembly load time.
				            if (count > 1)
					            throw TypeLoadException (
						            String::Format ("Type '{0}' has more than one method with the following attribute: '{1}'.", type->AssemblyQualifiedName, attr->FullName));

				            t = t->BaseType;
			            }

			            // optimize memory usage
			            return list->Count == 0 ? null : list;
		            }

		            static void Invoke (Collections::ArrayList* list, object* target, StreamingContext* context)
		            {
			            if (list == null)
				            return;

			            CallbackHandler handler = null;

			            // construct a delegate from the specified list
			            foreach (MethodInfo* mi : list) {
				            handler = (CallbackHandler)
					            Delegate::Combine (
						            Delegate::CreateDelegate (typeof (CallbackHandler), target, mi),
						            handler);
			            }

			            handler (context);
		            }

		            public: void RaiseOnSerializing (object* target, StreamingContext* contex)
		            {
			            Invoke (onSerializingList, target, contex);
		            }

		            public: void RaiseOnSerialized (object* target, StreamingContext* contex)
		            {
			            Invoke (onSerializedList, target, contex);
		            }

		            public: void RaiseOnDeserializing (object* target, StreamingContext* contex)
		            {
			            Invoke (onDeserializingList, target, contex);
		            }

		            public: void RaiseOnDeserialized (object* target, StreamingContext* contex)
		            {
			            Invoke (onDeserializedList, target, contex);
		            }

		            public: static GC_PTR<SerializationCallbacks> GetSerializationCallbacks (Type* t)
		            {
			            GC_PTR<SerializationCallbacks> sc = (SerializationCallbacks*) cache [t];
			            if (sc != null)
				            return sc;

			            // Slow path, new entry, we need to copy
			            lock (cache_lock){
				            sc = (SerializationCallbacks)  cache [t];
				            if (sc == null) {
					            GC_PTR<Collections::Hashtable> copy = (Collections::Hashtable*) cache->Clone ();
				
					            sc = new SerializationCallbacks (t);
					            (*copy)[t] = sc;
					            cache = copy;
				            }
				            return sc;
			            }
		            }
	            };
            }
		}
	}
}

#endif